﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace XMLScript
{
    public sealed class XMLScripter
    {
        string node_method, node_method_path, node_method_return;
        string node_parameter, node_parameter_variable;
        string node_if, node_if_condition, node_if_variable;
        Dictionary<string, string[]> redefine = new Dictionary<string, string[]>();

        public XMLScripter(XmlReader reader)
        {
            using (reader)
            {
                string name;
                bool started = false;

                while (reader.Read())
                {
                    if (reader.MoveToContent() == XmlNodeType.Element)
                    {
                        name = reader.Name.ToLower();
                        if (name == "xmlscripter")
                        {
                            if (started)
                                throw new Exception("You may only have one node of type 'xmlscripter'");
                            started = true;
                        }
                        else if (!started)
                            throw new Exception("You must nest all nodes inside a parent node of type 'xmlscripter'");
                        else if (name == "define_nodes")
                        {
                            string type = reader.GetAttribute("type").ToLower();
                            if (type == "method")
                                node_method = reader.ReadString().ToLower();
                            else if (type == "method_path")
                                node_method_path = reader.ReadString().ToLower();
                            else if (type == "method_return")
                                node_method_return = reader.ReadString().ToLower();
                            else if (type == "parameter")
                                node_parameter = reader.ReadString().ToLower();
                            else if (type == "parameter_variable")
                                node_parameter_variable = reader.ReadString().ToLower();
                            else if (type == "if")
                                node_if = reader.ReadString().ToLower();
                            else if (type == "if_condition")
                                node_if_condition = reader.ReadString().ToLower();
                            else if (type == "if_variable")
                                node_if_variable = reader.ReadString().ToLower();
                            else
                                throw new NotSupportedException("define_nodes attribute: type -> " + type);
                        }
                        else if (name == "redefine")
                        {
                            string classType = reader.GetAttribute("classType"),
                                methodName = reader.GetAttribute("methodName"),
                                assemblyPath = reader.GetAttribute("assemblyPath");
                            redefine.Add(reader.ReadString(), new string[] { assemblyPath, classType, methodName });
                        }
                        else
                            throw new NotSupportedException("Nodes of type '" + name + "' are not supported");
                    }
                }
            }
        }

        public sealed class Engine
        {
            XMLScripter settings;
            XmlReader reader = null;
            string parent;

            public Engine(XMLScripter settings)
            {
                this.settings = settings;
            }

            public void Reset(XmlReader reader, string parent)
            {
                this.reader = reader;
                this.parent = parent.ToLower();
            }

            public void Execute(Dictionary<string, object> variables)
            {
                if (reader == null)
                    throw new Exception("The Engine must be reset before it can run code again");
                var vars = new List<Dictionary<string, object>>();
                vars.Add(variables);
                DoWork(vars, reader, parent);

                reader = null;
            }
            private List<Dictionary<string, object>> DoWork(List<Dictionary<string, object>> variables, XmlReader reader, string parent)
            {
                variables.Add(new Dictionary<string, object>());

                string name;
                bool started = false;
                while (reader.Read())
                {
                    if (reader.MoveToContent() == XmlNodeType.Element)
                    {
                        name = reader.Name.ToLower();
                        if (name == parent)
                        {
                            if (started)
                                throw new Exception("You may only have one node of type '" + parent + "'");
                            started = true;
                        }
                        else if (!started)
                            throw new Exception("You must nest all nodes inside a parent node of type '" + parent + "'");
                        else if (name == settings.node_if)
                        {
                            string useVar = reader.GetAttribute(settings.node_if_variable),
                                compute = reader.GetAttribute(settings.node_if_condition);

                            bool recurse = false;
                            if (useVar != null)
                                recurse = (bool)Convert.ChangeType(GetVar(variables, compute), typeof(bool));
                            else if (compute.ToLower() == "true")
                                recurse = true;

                            if (recurse)
                                variables = DoWork(variables, reader.ReadSubtree(), "if");
                            else
                            {
                                int close = 1;
                                while (reader.Read())
                                {
                                    switch (reader.MoveToContent())
                                    {
                                        case XmlNodeType.Element:
                                            if (reader.Name.ToLower() == settings.node_if)
                                                close++;
                                            break;
                                        case XmlNodeType.EndElement:
                                            if (reader.Name.ToLower() == settings.node_if)
                                                close--;
                                            break;
                                    }

                                    if (close == 0)
                                        break;
                                }
                            }
                        }
                        else if (name == settings.node_method)
                        {
                            string methodPath = reader.GetAttribute(settings.node_method_path),
                                returnVarName = reader.GetAttribute(settings.node_method_return);
                            if (settings.redefine.ContainsKey(methodPath))
                            {
                                methodPath = settings.redefine[methodPath][0] + "|"
                                    + settings.redefine[methodPath][1] + "|"
                                    + settings.redefine[methodPath][2];
                            }

                            var parts = methodPath.Split('|');

                            List<object> parameters = new List<object>();
                            XmlReader parameterReader = reader.ReadSubtree();
                            parameterReader.Read();
                            while (parameterReader.Read())
                            {
                                if (reader.MoveToContent() == XmlNodeType.Element)
                                {
                                    if (reader.Name.ToLower() == settings.node_parameter)
                                    {
                                        string useVariable = reader.GetAttribute(settings.node_parameter_variable);

                                        if (useVariable != null)
                                        {
                                            string varName = reader.ReadString();
                                            if (!HasVar(variables, varName))
                                                throw new Exception("Variable has not been declared: " + varName);
                                            parameters.Add(GetVar(variables, varName));
                                        }
                                        else
                                            parameters.Add(reader.ReadString());
                                    }
                                }
                            }

                            object ret = Utility.InvokeMethod(parts[0], parts[1], parts[2], parameters.ToArray());
                            if (returnVarName != null)
                                SetVar(variables, returnVarName, ret);
                        }
                    }
                }

                variables.RemoveAt(variables.Count - 1);
                return variables;
            }
            private bool HasVar(List<Dictionary<string, object>> variables, string varName)
            {
                foreach (var item in variables)
                {
                    if (item.ContainsKey(varName))
                        return true;
                }
                return false;
            }
            private object GetVar(List<Dictionary<string, object>> variables, string varName)
            {
                if (!HasVar(variables, varName))
                    return false;

                foreach (var item in variables)
                {
                    if (item.ContainsKey(varName))
                        return item[varName];
                }
                return null;
            }
            private void SetVar(List<Dictionary<string, object>> variables, string varName, object data)
            {
                if (HasVar(variables, varName))
                {
                    for (int i = 0; i < variables.Count; i++ )
                    {
                        if (variables[i].ContainsKey(varName))
                        {
                            if (i == 0)
                                throw new Exception("Program-side variables may not be modified");
                            variables[i][varName] = data;
                            break;
                        }
                    }
                }
                else
                {
                    variables[variables.Count - 1].Add(varName, data);
                }
            }

            public bool ExecutionComplete { get { return reader == null; } }
        }
    }
}
